﻿//-----------------------------------------------------------------------
// <copyright>
// This file is subject to the terms and conditions of the Microsoft Public License (MS-PL).
// See http://www.microsoft.com/opensource/licenses.mspx#Ms-PL for more details.
// </copyright>
//----------------------------------------------------------------------- 
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Text;

namespace Fulcrum.DispatcherProxies
{
    public class DispatcherProxyTarget
    {
        Type targetType;

        public DispatcherProxyTarget(Type targetType, DispatcherProxyTarget baseProxy)
        {
            this.targetType = targetType;
            this.BaseProxy = baseProxy;
        }

        public string ProxyName
        {
            get
            {
                return String.Format("{0}Proxy", this.targetType.Name);
            }
        }

        public DispatcherProxyTarget BaseProxy
        {
            get;
            private set;
        }

        public bool IsAbstract
        {
            get
            {
                return this.targetType.IsAbstract;
            }
        }

        public string WrappedType
        {
            get
            {
                return this.targetType.FullName;
            }
        }

        public ICollection<DispatcherProxyConstructorInfo> GetConstructors()
        {
            var constructors = this.targetType.GetConstructors(BindingFlags.Public | BindingFlags.Instance);

            var dispatcherProxyConstructors = new SortedDictionary<string, DispatcherProxyConstructorInfo>();

            foreach (var constructor in constructors)
            {
                if (false == DispatcherProxyGenerationPolicy.AreParametersSupported(constructor.GetParameters()))
                {
                    continue;
                }
		
                DispatcherProxyConstructorInfo dispProxyConstructorInfo = new DispatcherProxyConstructorInfo(constructor, ReflectionHelp.IsMethodInAnyBaseType(constructor));

                var key = CreateMethodKey(constructor);

                dispatcherProxyConstructors.Add(key, dispProxyConstructorInfo); 
            }

            return dispatcherProxyConstructors.Values;
        }

        public ICollection<DispatcherProxyMethodInfo> GetInstanceMethods()
        {
            var methods = this.targetType.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.DeclaredOnly);

            var processedMethods = new SortedDictionary<string, DispatcherProxyMethodInfo>();

            foreach (var method in methods)
            {
                if (false == DispatcherProxyGenerationPolicy.IsSupportedMethod(method))
                {
                    continue;
                }

                if (!ReflectionHelp.IsOverridenMethod(method))
                {
                    string methodKey = CreateMethodKey(method);

                    processedMethods[methodKey] = new DispatcherProxyMethodInfo(method, ReflectionHelp.IsMethodInAnyBaseType(method));
                }
            }

            return processedMethods.Values;
        }

        public ICollection<DispatcherProxyEventInfo> GetInstanceEvents()
        {
            var events = this.targetType.GetEvents(BindingFlags.Instance | BindingFlags.Public | BindingFlags.DeclaredOnly);

            var proxyEvents = new Dictionary<string, DispatcherProxyEventInfo>();

            foreach (var @event in events)
            {
                var proxyEvent = new DispatcherProxyEventInfo(@event);
                proxyEvents[@event.Name] = proxyEvent;
            }

            return proxyEvents.Values;
        }

        public ICollection<DispatcherProxyPropertyInfo> GetInstanceProperties()
        {
            var properties = this.targetType.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.DeclaredOnly);

            var processedProperties = new Dictionary<string, PropertyInfo>();

            foreach (var property in properties)
            {
                string key = CreatePropertyKey(property);

                processedProperties[key] = property;
            }

            var proxyProperties = new SortedDictionary<string, DispatcherProxyPropertyInfo>();

            var propertyMethods = new Dictionary<string, PropertyInformationPair>();

            foreach (var method in this.targetType.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.DeclaredOnly))
            {
                if (method.IsSpecialName && !ReflectionHelp.IsOverridenMethod(method))
                {
                    PropertyInformationPair memberInfo = new PropertyInformationPair(method.Name, ReflectionHelp.IsMethodInAnyBaseType(method));
                    propertyMethods[method.Name] = memberInfo;
                }
            }

            foreach (var property in processedProperties.Values)
            {
                string setMethodName = String.Format("set_{0}", property.Name);
                bool setMethodPresent = propertyMethods.ContainsKey(setMethodName);

                string getMethodName = String.Format("get_{0}", property.Name);
                bool getMethodPresent = propertyMethods.ContainsKey(getMethodName);

                //
                // If an overriden property doesn't add a getter or setter then we don't need
                // to generate a proxy part.
                //
                if (false == setMethodPresent && false == getMethodPresent)
                {
                    continue;
                }

                bool isNewProperty = false;

                if (setMethodPresent)
                {
                    isNewProperty = propertyMethods[setMethodName].IsNew;
                }

                if (getMethodPresent)
                {
                    isNewProperty |= propertyMethods[getMethodName].IsNew;
                }

                var proxyProperty = new DispatcherProxyPropertyInfo(property, getMethodPresent, setMethodPresent, isNewProperty);
                proxyProperties.Add(proxyProperty.Name, proxyProperty);
            }

            return proxyProperties.Values;
        }

        string CreateMethodKey(MethodBase method)
        {
            var key = new StringBuilder();
            key.Append(method.Name);

            foreach (var parameter in method.GetParameters())
            {
                key.Append(parameter.ParameterType.Name);
            }

            return key.ToString();
        }

        string CreatePropertyKey(PropertyInfo property)
        {
            var key = new StringBuilder();
            key.Append(property.Name);
            key.Append(property.PropertyType.FullName);

            return key.ToString();
        }

        class PropertyInformationPair
        {
            public PropertyInformationPair(string memberName, bool isNewMember)
            {
                this.Name = memberName;
                this.IsNew = isNewMember;
            }

            public string Name
            {
                private set;
                get;
            }

            public bool IsNew
            {
                private set;
                get;
            }
        }
    }
}
